/*
 * Copyright 2008 Carnegie Mellon University
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, 
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package edu.cmu.lti.sepia.servlet;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;

import edu.cmu.lti.sepia.aclia.FormatValidator;
import edu.cmu.lti.sepia.data.db.SQL;
import edu.cmu.lti.sepia.data.db.TaskDAO;
import edu.cmu.lti.sepia.data.db.UploadedFileDAO;
import edu.cmu.lti.sepia.data.resource.ResourceManager;
import edu.cmu.lti.sepia.data.resource.RunType;
import edu.cmu.lti.sepia.infra.system.model.UploadedFile;
import edu.cmu.lti.sepia.infra.topic.model.Task;
import edu.cmu.lti.sepia.util.ExceptionUtil;
import edu.cmu.lti.sepia.util.Logger;
import edu.cmu.lti.sepia.util.ServletUtil;

public class Uploader extends HttpServlet {

  private static final long serialVersionUID = 1L;

  private final static int THRESHOLD = 50*1024*1024;
  private final static long MAXSIZE = 100*1024*1024;
  
  private final static String DELETE_SUCCEEDED = "<strong style=\"color:#ff0000\">Delete succeeded.</strong>\n";
  private final static String DELETE_ERROR = "<strong>ERROR: There was a problem when deleting the file.</strong>\n";
  private final static String UPLOAD_SUCCEEDED = "<br><strong style=\"color:#ff0000\">Upload succeeded.</strong><br>\n";
  private final static String UPLOAD_FORMAT_ERROR = "<br><strong style=\"color:#ff0000\">Upload failed. Please fix the problem, after reading the error message and the format spec.</strong><br>\n";
  private final static String UPLOAD_SYSTEM_ERROR = "<br><strong style=\"color:#ff0000\">Upload failed. Please report the error message and send the file to the system admin by email.</strong><br>\n";
  private final static String PERMISSION_ERROR = "<strong>ERROR: Same file with different owner already exists.</strong><br><br>\n";
  private final static String DUPLICATION_WARNING = "<strong>WARNING: Same file already exists.</strong> Previous submission will be overwritten.<br><br>\n";
  private final static String WRONG_TRG_LANG = "<strong>ERROR: Task's target language \"@L1@\" does not match file's target language \"@L2@\"</strong><br><br>\n";
  private final static String WRONG_ENCODING = "<strong>ERROR: Encoding invalid. Please encode the file in Unicode (UTF-8).</strong><br><br>\n";

  private final boolean isGeneralFileOnly = true;
  
  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    
    ServletUtil s = new ServletUtil( request );
    int userId = s.readInt("userId", -1);
    int taskId = s.readInt("taskId", -1);
    
    Commons.printHeader(request, response, "Uploader", 2);
    
    SQL sql = new SQL();
    try {
      StringBuilder report = new StringBuilder();
      String deleteFileId = request.getParameter("deleteFileId");
      try {
        if (deleteFileId!=null) {
          UploadedFile f = UploadedFileDAO.findById(sql, Integer.parseInt(deleteFileId));
          Logger.log( sql, request.getSession(), "DELETE FILE", f.getFileName()+" ("+f.getType()+")" );
          UploadedFileDAO.deleteFile(sql, Integer.parseInt(deleteFileId));
          report.append(DELETE_SUCCEEDED);
        }
      } catch (SQLException e) {
        report.append(DELETE_ERROR);
      }
      if (report.length()>0) {
        response.getOutputStream().println(
              "<div style=\"border:2px solid #ff0000;padding10px;\">" +
              report.toString()+"</div>");
      }
      
      response.getOutputStream().println( getUploadForm(request, response, request.getContextPath()+"/io/uploader.html", "") );
      printMySubmission( sql, request.getRemoteUser(), userId, taskId, request, response );
    } catch (Exception e) {
      throw new IOException(e);
    } finally {
      try {
        sql.close();
      } catch (Exception e){e.printStackTrace();}
    }

    Commons.printFooter(request, response);
  }
  
  @SuppressWarnings("unchecked")
  public UploadResult upload(HttpServletRequest request, HttpServletResponse response, 
          SQL sql, int userId, int taskId ) throws ServletException, IOException {

    UploadResult result = new UploadResult();
    StringBuilder report = new StringBuilder();
    InputStream is = null;
    try {
      DiskFileItemFactory factory = new DiskFileItemFactory();
      factory.setSizeThreshold(THRESHOLD);
      
      ServletFileUpload upload = new ServletFileUpload(factory);
      upload.setFileSizeMax(MAXSIZE);
      upload.setHeaderEncoding("utf-8");
      
      Map<String,String> params = new HashMap<String,String>();
      List<FileItem> items = (List<FileItem>) upload.parseRequest(request);
      
      String filename = null;
      
      for (FileItem item : items) {
        if (!item.isFormField()) {
          is = item.getInputStream();
          filename = item.getName();
          factory.setRepository(File.createTempFile(filename, ".tmp"));
          params.put("filename", filename);
        } else {
          params.put(item.getFieldName(), item.getString());
        }
      }
      
      result.params = params;
      String filetype = params.get("filetype");
      String description = params.get("description");
      
      //this part is skipped for now
      if (!isGeneralFileOnly) {
        RunType type = RunType.valueOf(filetype);             
        // Parse
        FormatValidator validator = new FormatValidator();
        validator.runValidation(is, filetype);
        description = validator.getDescription();
        
        if (taskId!=-1){
          Task t = TaskDAO.findById(sql, taskId);
          if (t!=null && (!t.getTrgLang().equalsIgnoreCase(validator.getTrgLang()))) {
            try {
              report.append(WRONG_TRG_LANG
                      .replaceFirst("@L1@", t.getTrgLang())
                      .replaceFirst("@L2@", validator.getTrgLang()));
            } catch (NullPointerException e) {
              report.append(WRONG_ENCODING);
            }
          }
        }
        filename = validator.getRunid()+".xml";
        report.append( validator.getReport() );
      }

      UploadedFile existingFile = UploadedFileDAO.findByTypeAndName(sql, filetype, filename);
      if (existingFile!=null) {
        if (existingFile.getUserId()!=userId) {
          report.append(PERMISSION_ERROR);
        } else {
          report.append(DUPLICATION_WARNING);
        }
      }
      
      boolean problemExists = report.toString().indexOf("ERROR:")!=-1;
      if (problemExists) {
        report.append(UPLOAD_FORMAT_ERROR);
      } else {
        int fileId = existingFile==null ? -1 : existingFile.getFileId();
        // Save to Disk
        UploadedFile uploadedFile = save(fileId, userId, taskId, items, filename, filetype, description);
        // Save file information in db
        try {
          String message = uploadedFile.getFileName()+" ("+uploadedFile.getType()+")";
          if (fileId>0) {
            UploadedFileDAO.updateFile(sql, uploadedFile);
            Logger.log( sql, request.getSession(), "UPLOAD FILE", message );
          } else {
            UploadedFileDAO.addFile(sql, uploadedFile);
            Logger.log( sql, request.getSession(), "UPLOAD FILE", message );
          }
          report.append(UPLOAD_SUCCEEDED);
        } catch (SQLException e) {
          report.append(UPLOAD_SYSTEM_ERROR+"<br>"+ExceptionUtil.exceptionToString(e));
        }
      }
    } catch (Exception e) {
      report.append(UPLOAD_SYSTEM_ERROR+"<br>"+ExceptionUtil.exceptionToString(e));
    } finally {
      if (is!=null ) {
        is.close();
      }
    }
    result.report = report.toString();

    return result;
  }
  
  public void doPost(HttpServletRequest request, HttpServletResponse response)
	  throws ServletException, IOException {

    Commons.printHeader(request, response, "Uploader", 2);
    
    ServletUtil s = new ServletUtil( request );
    int userId = s.readInt("userId", -1);
    int taskId = s.readInt("taskId", -1);
    
    SQL sql = new SQL();
    try {
      UploadResult ur = upload( request, response, sql, userId, taskId );
      String filetype = ur.params.get("filetype");
      String filename = ur.params.get("filename");
      response.getOutputStream().println( 
         "<div style=\"border:2px solid #ff0000;padding:10px\">" +
         "<h4>Submission result of \""+filename+"\"</h4>\n"+ur.report + "</div>\n" +
         getUploadForm(request, response, request.getContextPath()+"/io/uploader.html", filetype) );
      printMySubmission( sql, request.getRemoteUser(), userId, taskId, request, response );
    } catch (Exception e) {
      throw new IOException(e);
    } finally {
      try {
        sql.close();
      } catch (Exception e){e.printStackTrace();}
    }
    
    Commons.printFooter(request, response);
    //redirect
	}
  
  public String getUploadForm(HttpServletRequest request, HttpServletResponse response, String actionPath, String filetype) 
    throws IOException {

    StringBuilder sb = new StringBuilder();
    sb.append("<br><h4>File Uploader</h4>\n");
    sb.append("Select the file type, specifiy the file path and then click submit.<br><br>");
    
    sb.append("<form action=\""+actionPath+"\" method=\"POST\" enctype=\"multipart/form-data\" onsubmit=\"if(!$('file').value){alert('Please select the file to upload');return false;};\">\n");    
    sb.append("<table cellpadding=\"3\">\n");
    sb.append("<tr><td width=\"100\">File type</td><td><select name=\"filetype\">\n");
    /*
    sb.append("<option value=\""+RunType.qa.toString()+"\""+(filetype.equalsIgnoreCase(RunType.qa.toString())?" selected":"")+">"+RunType.qa.getLabel()+"</option>\n");
    sb.append("<option value=\""+RunType.ir4qa.toString()+"\""+(filetype.equalsIgnoreCase(RunType.ir4qa.toString())?" selected":"")+">"+RunType.ir4qa.getLabel()+"</option>\n");
    sb.append("<option value=\""+RunType.cclqa.toString()+"\""+(filetype.equalsIgnoreCase(RunType.cclqa.toString())?" selected":"")+">"+RunType.cclqa.getLabel()+"</option>\n");
    sb.append("<option value=\""+RunType.collaboration.toString()+"\""+(filetype.equalsIgnoreCase(RunType.collaboration.toString())?" selected":"")+">"+RunType.collaboration.getLabel()+"</option>\n");
    */
    sb.append("<option value=\"other\""+(filetype.equalsIgnoreCase("other")?" selected":"")+">Other</option>\n");
    sb.append("<!--Filetype = "+filetype+"-->\n");
    sb.append("</select></td></tr>\n");
    sb.append("<tr><td>File</td><td><input type=\"file\" name=\"file\" id=\"file\"></td></tr>\n");
    sb.append("<tr><td>Description</td><td><input type=\"text\" name=\"description\" id=\"description\" style=\"width:400px\"></td></tr>\n");
    sb.append("<tr><td colspan=2><input type=\"submit\" value=\"   submit   \"></td></tr>\n");
    sb.append("</table>");
    sb.append("</form>\n");
    return sb.toString();
  }
  
  private void printMySubmission(SQL sql, String userName, int userId, int taskId, HttpServletRequest request, HttpServletResponse response) 
  throws Exception {

    StringBuilder sb = new StringBuilder();
    sb.append("<hr><br><br><h4>Files submitted by "+userName+"</h4>\n");
    List<UploadedFile> files = UploadedFileDAO.findByUserAndTaskId(sql, userId, taskId);
    if (files.size()==0) {
      sb.append("No files have been submitted yet.");
    } else {
      sb.append("<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"datatable\">\n");
      sb.append("<thead>"+getTableHeader()+"</thead>\n");
      sb.append("<tbody>\n");
      
      for (int i=0;i<files.size(); i++) {
        UploadedFile file = files.get(i);
        sb.append( "<tr class=\""+(i%2==1?"odd":"even")+"\">\n" );
        sb.append( "<td nowrap>"+file.getType()+"</td>\n" );
        sb.append( "<td nowrap>"+file.getFileName()+"</td>\n" );
        sb.append( "<td>"+file.getDescription()+"</td>\n" );
        sb.append( "<td nowrap>"+file.getDate()+"</td>\n" );
        sb.append( "<td nowrap>"+FileUtils.byteCountToDisplaySize(file.getSize())+"</td>\n" );
        sb.append( "<td><form action=\""+request.getRequestURI()+"\" method=\"GET\">"
                + "<input type=\"hidden\" name=\"deleteFileId\" value=\""+file.getFileId()+"\">"
        		    + "<input onclick=\"javascript:return confirm('Confirmation: delete this file?')\" type=\"submit\" value=\"DELETE\"></form></td>\n" );
        sb.append( "</tr>\n" );
      }
      sb.append("</tbody>\n");
      //sb.append("<tfoot>"+getTableHeader()+"</tfoot>");
      sb.append("</table>\n");
      sb.append("<br>Go to the download page to download the content of above files.\n"); 
      
    }
    response.getOutputStream().println(sb.toString());
  }
  
  private String getTableHeader() {
    StringBuilder sb = new StringBuilder();
    sb.append("<tr>\n");
    sb.append("<th>Type</th>\n");
    sb.append("<th>Name</th>\n");
    sb.append("<th>Description</th>\n");
    sb.append("<th>Date modified</th>\n");
    sb.append("<th>Size</th>\n");
    sb.append("<th>Action</th>\n");
    sb.append("</tr>\n");
    return sb.toString();
  }
   
  private UploadedFile save(int fileId, int userId, int taskId, List<FileItem> items,
    String fileName, String filetype, String description) 
    throws Exception {
    
    String filePath = filetype+File.separator+fileName;
    File absolutePath = new File(ResourceManager.uploadRepository, filePath);
    for (FileItem item : items) {
      if (!item.isFormField()) {
        File newDir = absolutePath.getParentFile();
        if (!newDir.exists()) {
          newDir.mkdirs();
        }
        item.write(absolutePath);
      }
    }
    
    String date = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
    int size =  (int)absolutePath.length();
    return new UploadedFile( fileId, userId, taskId, date, fileName, filePath, filetype, description, size, null ); 
  }
}

class UploadResult {
  public String report;
  public Map<String,String> params;
}
